package com.yuncheng.shiro.config;

import com.yuncheng.constant.CommonConstant;
import com.yuncheng.shiro.authc.ShiroRealm;
import com.yuncheng.shiro.authc.ShiroRealmForRest;
import com.yuncheng.shiro.authc.aop.JwtFilter;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisClusterManager;
import org.crazycake.shiro.RedisManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    private static final Logger log = LoggerFactory.getLogger(ShiroConfig.class);

    //业务自定义可匿名访问的url
    @Value("${yuncheng.shiro.anonAccessUrls:}")
    private String anonAccessUrls;

    @Value("${spring.redis.port:}")
    private String port;

    @Value("${spring.redis.host:}")
    private String host;

    @Value("${spring.redis.password:}")
    private String redisPassword;

    //匿名访问url系统级别定义
    private static final Map<String, String> anonMap;
    static
    {
        anonMap = new LinkedHashMap<>();
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/cas/client/validateLogin", "anon");//cas验证登录
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/checkCaptcha", "anon"); //登录验证码接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/randomImage/**", "anon"); //登录验证码接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/login", "anon"); //登录接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/oauth2/login", "anon"); //oauth2登录接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/login2", "anon"); //登录接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/mLogin", "anon"); //登录接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/thirdPartLogin", "anon"); //登录接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/appUserLogin", "anon"); //登录接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/robotLogin", "anon"); //登录接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/phoneLogin", "anon");//手机登录
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/sms", "anon");//短信验证码
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/logout", "anon"); //登出接口
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/getEncryptedString", "anon"); //获取加密串
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/user/checkOnlyUser", "anon");//校验用户是否存在
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/user/register", "anon");//用户注册
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/user/querySysUser", "anon");//根据手机号获取用户信息
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/user/phoneVerification", "anon");//用户忘记密码验证手机号
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/user/passwordChange", "anon");//用户更改密码
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/common/view/**", "anon");//图片预览不限制token
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/common/static/logs/**", "anon");//错误日志不限制token
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/common/download/**", "anon");//文件下载不限制token
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/demo/**", "anon");//demo测试模块
        anonMap.put(CommonConstant.PREFIX_EFORM + "/manage/localeProvider/getLocales", "anon");// 获取多语言数据
        anonMap.put(CommonConstant.PREFIX_EFORM + "/manage/actAppHelp/getSyncList", "anon");// 获取帮助示例
        anonMap.put(CommonConstant.PREFIX_EFORM + "/manage/actAppHelp/uploadHelps", "anon");// 贡献帮助示例
        anonMap.put("/", "anon");
        anonMap.put("/doc.html", "anon");
        anonMap.put("/**/*.js", "anon");//静态资源文件
        anonMap.put("/**/*.css", "anon");//静态资源文件
        anonMap.put("/**/*.html", "anon");//静态资源文件
        anonMap.put("/**/*.svg", "anon");//静态资源文件
        anonMap.put("/**/*.pdf", "anon");//静态资源文件
        anonMap.put("/**/*.jpg", "anon");//静态资源文件
        anonMap.put("/**/*.png", "anon");//静态资源文件
        anonMap.put("/**/*.ico", "anon");//静态资源文件
        anonMap.put("/**/*.ttf", "anon"); //字体格式
        anonMap.put("/**/*.woff", "anon");//字体格式
        anonMap.put("/druid/**", "anon");//
        anonMap.put("/swagger**/**", "anon");//swagger访问
        anonMap.put("/webjars/**", "anon");//swagger访问
        anonMap.put("/v3/**", "anon");
        anonMap.put("/actuator/metrics/**", "anon");//性能监控
        anonMap.put("/actuator/httptrace/**", "anon");//性能监控
        anonMap.put("/actuator/redis/**", "anon");//性能监控
        anonMap.put("/actuator/health/**", "anon");//性能监控
        anonMap.put("/websocket/**", "anon"); //websocket通信
        anonMap.put("/api/screen/**","anon");//大屏调用
        anonMap.put("/api/system/common/uploadScreen","anon");//大屏上传文件

        //图形错位验证码
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/captcha/**", "anon");

        // 日志查询暂时不验证权限，Saas查看使用
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/log/list", "anon");

        //TODO 排除bpm请求数据的集成接口，后续需要判断，接口中不能含有和token或登录用户有关的判断和取值
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/sysOrgConver/**", "anon");
        //获取项目名称等系统参数
        anonMap.put(CommonConstant.PREFIX_SYSTEM + "/sysconfig/querySysConfigByCodeList", "anon");
        //ureport
        anonMap.put("/ureport/**", "anon");
        anonMap.put("/actuator/**", "anon"); //spring boot admin 性能监控
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //业务自定义匿名访问路径
        if (!StringUtils.isEmpty(anonAccessUrls)) {
            String[] permissionUrl = anonAccessUrls.split(",");
            for (String url : permissionUrl) {
                anonMap.put(url, "anon");
            }
        }

        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        filterMap.put("jwt", new JwtFilter(1==1));
        shiroFilterFactoryBean.setFilters(filterMap);
        //从上向下顺序执行,上面是可匿名访问的，除此之外是需要授权访问
        anonMap.put("/**", "jwt");

        // 未授权界面返回JSON
        shiroFilterFactoryBean.setUnauthorizedUrl(CommonConstant.PREFIX_SYSTEM + "/common/403");
        shiroFilterFactoryBean.setLoginUrl(CommonConstant.PREFIX_SYSTEM + "/common/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(anonMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    @ConditionalOnClass(name = {"com.yuncheng.system.api.local.SystemLocal"})
    public ShiroRealm shiroRealm() {
        return new ShiroRealm();
    }

    @Bean
    @ConditionalOnMissingClass(value = {"com.yuncheng.system.api.local.SystemLocal"})
    public ShiroRealmForRest shiroRealmForRest() {
        return new ShiroRealmForRest();
    }

    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(AuthorizingRealm shiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm);

        //关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //使用自定义redis缓存实现
        securityManager.setCacheManager(redisCacheManager());
        //解决IE浏览器下报错的问题
        securityManager.setRememberMeManager(null);
        return securityManager;
    }

    /**
     * 下面的代码是添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
         //解决重复代理问题， 添加前缀判断不匹配 任何Advisor
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor");
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     * 配置shiro redisManager，使用shiro-redis开源插件
     */
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        //redis中针对不同用户缓存(此处的id需要对应user实体中的id字段,用于唯一标识)
        redisCacheManager.setPrincipalIdFieldName("id");
        //用户权限信息缓存时间
        redisCacheManager.setExpire(200000);

        //vvv add 2023-04-10
        if (StringUtils.isNotEmpty(redisNamespace)) {
            // 从spring.cache.redis获取KeyPrefix，来设置RedisCacheManager的KeyPrefix
            redisCacheManager.setKeyPrefix(redisNamespace + RedisCacheManager.DEFAULT_CACHE_KEY_PREFIX);
            log.info("===============shiro创建RedisCacheManager,KeyPrefix=" + redisNamespace + RedisCacheManager.DEFAULT_CACHE_KEY_PREFIX);
        }
        //^^^ add 2023-04-10

        return redisCacheManager;
    }

    /**
     * 配置shiro redisManager，使用shiro-redis开源插件
     */
    @Bean
    public IRedisManager redisManager() {
        //vvv add 2023-04-10
        IRedisManager result = null;
        if (isRedisCluster()) {
            // 配置使用的是rediscluster
            RedisClusterManager redisClusterManager = new RedisClusterManager();

            String hosts = String.join(",", redisProperties.getCluster().getNodes());
            log.info("===============shiro创建RedisClusterManager,连接RedisCluster= " + hosts);
            redisClusterManager.setHost(hosts);

            if (!StringUtils.isEmpty(redisProperties.getPassword())) {
                redisClusterManager.setPassword(redisProperties.getPassword());
            }
            result = redisClusterManager;
        } else {
            //^^^ add 2023-04-10
            log.info("===============shiro创建RedisManager,连接Redis= " + host + ":" + port);
            RedisManager redisManager = new RedisManager();
            redisManager.setHost(host + ":" + port);
            if (!StringUtils.isEmpty(redisPassword)) {
                redisManager.setPassword(redisPassword);
            }
            result = redisManager;
        }
        return result;
    }

    //vvv add 2023-04-10

    @Autowired(required = false)
    private RedisProperties redisProperties;

    @Value("${redis_namespace:}")
    private String redisNamespace;

    private boolean isRedisCluster() {
        if ((null != redisProperties) && (null != redisProperties.getCluster())) {
            if (null != redisProperties.getCluster().getNodes()) {
                return redisProperties.getCluster().getNodes().size() > 0;
            }
        }
        return false;
    }

    //^^^ add 2023-04-10

}
